package com.heima.article.service.impl;
import java.util.Date;

import com.alibaba.fastjson.JSON;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.HotArticleService;
import com.heima.common.constants.article.ArticleConstants;
import com.heima.common.constants.article.HotArticleConstants;
import com.heima.common.exception.CustException;
import com.heima.feigns.AdminFegin;
import com.heima.model.admin.pojos.AdChannel;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.ArticleVisitStreamMess;
import com.heima.model.mess.UpdateArticleMess;
import com.heima.utils.common.DateUtils;
import com.xxl.job.core.biz.model.ReturnT;
import javafx.scene.layout.BackgroundRepeat;
import javassist.expr.Cast;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.operator.AADProcessor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.swing.text.html.HTML;
import java.lang.reflect.InvocationTargetException;
import java.text.DateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;

/**
 * @author lo
 * @date 2021/10/7
 */
@Service
@Slf4j
public class HotArticleServiceImpl implements HotArticleService {

    @Autowired
    ApArticleMapper apArticleMapper;

    @Autowired
    RedisTemplate<String, String> redisTemplate;

    @Autowired
    AdminFegin adminFegin;

    /**
     * 计算热点文章
     */
    @Override
    public void computeHotArticle() {
        //1.查询前五天的文章
        //获取前五天时间的字符串
        String date = LocalDateTime.now().minusDays(5).format(DateTimeFormatter.ofPattern("yyyy-MM-dd 00:00:00"));
        List<ApArticle> apArticles = apArticleMapper.selectArticleByDate(date);

        //2.计算每篇文章的分值
        List<HotArticleVo> hotArticleVoList = computeArticleScore(apArticles);

        //3.缓存每一个频道下的文章
        cacheTagToRedis(hotArticleVoList);

    }

    /**
     * 3.缓存每一个频道下的文章
     *
     * @param hotArticleVoList
     */
    private void cacheTagToRedis(List<HotArticleVo> hotArticleVoList) {
        //1.远程调用fegin接口,查询所有频道列表
        ResponseResult channelList = adminFegin.getChannelList();
        if (channelList.getCode() == 0) {
            //将获取的object转换为字符串,再讲字符串转换为集合
            List<AdChannel> adChannels = JSON.parseArray(JSON.toJSONString(channelList.getData()), AdChannel.class);
            //遍历频道列表
            adChannels.forEach(adChannel -> {
                //获取每个频道对应的文章集合
                List<HotArticleVo> collect = hotArticleVoList.stream().filter(hotArticleVo -> hotArticleVo.getChannelId().equals(adChannel.getId()))
                        .collect(Collectors.toList());
                //缓存对应频道热点文章(前30条)
                sortAndCache(collect, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + adChannel.getId());
            });
            //缓存所有热点文章集合(前30条)
            sortAndCache(hotArticleVoList, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
        }
    }

    /**
     * 缓存文章数据
     *
     * @param collect
     * @param s
     */
    private void sortAndCache(List<HotArticleVo> collect, String s) {
        //根据分数排序,收集前30条热点数据
        List<HotArticleVo> hotArticleVos = collect.stream()
                .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(30)
                .collect(Collectors.toList());
        //存入redis
        redisTemplate.opsForValue().append(s, JSON.toJSONString(hotArticleVos));
    }

    /**
     * 计算所有文章分值
     *
     * @param apArticles
     * @retur
     */
    private List<HotArticleVo> computeArticleScore(List<ApArticle> apArticles) {
        return apArticles.stream().map(apArticle -> {
            HotArticleVo hotArticleVo = new HotArticleVo();
            BeanUtils.copyProperties(apArticle, hotArticleVo);
            // 2.1计算文章分值算法
            Integer score = computeScore(apArticle);
            hotArticleVo.setScore(score);
            return hotArticleVo;
        }).collect(Collectors.toList());
    }

    /**
     * 2.1计算文章分值算法
     *
     * @param apArticle
     * @return
     */
    private Integer computeScore(ApArticle apArticle) {
        //1.点赞
        Integer likes = apArticle.getLikes();
        int scores = 0;
        if (likes != null) {
            scores += likes * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        //2.查看
        Integer views = apArticle.getViews();
        if (views != null) {
            scores += views * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }
        //3.评论
        Integer comment = apArticle.getComment();
        if (comment != null) {
            scores += comment * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        //4.收藏
        Integer collection = apArticle.getCollection();
        if (collection != null) {
            scores += collection * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;

        }
        return scores;
    }

    @Autowired
    HotArticleService hotArticleService;

    /**
     * 定时更新文章热度
     */
    @Override
    public void updateHotArticleHandler() {
        log.info("热文章分值更新 调度任务开始执行....");
        // 1. 查询redis行为列表中的行为数据
        List<UpdateArticleMess> updateArticleMessList = getUpdateArticleMesses();
        // 2. 按文章分组  每个文章的所有行为 进行聚合处理
        List<ArticleVisitStreamMess> waitUpdateScoreData = getArticleVisitStreamMesses(updateArticleMessList);
        if (CollectionUtils.isEmpty(waitUpdateScoreData)) {
            log.info("热文章分值更新: 太冷清了 未产生任何文章行为 调度任务完成....");
            return;
        }
        //3. 更新数据库文章分值
        for (ArticleVisitStreamMess mess : waitUpdateScoreData) {
            updateApArticle(mess);
        }
        log.info("热文章分值更新: 调度任务完成....");
    }


    /**
     * 重新计算文章热度  更新redis缓存
     *
     * @param mess
     */
    @Override
    public void updateApArticle(ArticleVisitStreamMess mess) {
        //1.查询文章数据
        ApArticle apArticle = apArticleMapper.selectById(mess.getArticleId());
        if (apArticle == null) {
            CustException.cust(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        //2.修改文章数据
        //点赞
        if (mess.getLike() != 0) {
            int like = (int) (apArticle.getLikes() == null ? mess.getLike() : apArticle.getLikes() + mess.getLike());
            apArticle.setLikes(like);
        }
        //阅读
        if (mess.getView() != 0) {
            int view = (int) (apArticle.getViews() == null ? mess.getView() : apArticle.getViews() + mess.getView());
            apArticle.setViews(view);
        }
        //收藏
        if (mess.getCollect() != 0) {
            int collect = (int) (apArticle.getCollection() == null ? mess.getCollect() : apArticle.getCollection() + mess.getCollect());
            apArticle.setCollection(collect);
        }
        //评论
        if (mess.getComment() != 0) {
            int comment = (int) (apArticle.getComment() == null ? mess.getComment() : apArticle.getComment() + mess.getComment());
            apArticle.setComment(comment);
        }
        //更新文章数据
        apArticleMapper.updateById(apArticle);
        //3.重新算分
        Integer score = computeScore(apArticle);
        //4.如果发布时间是今天,分数*3
        String publishTime = DateUtils.dateToString(apArticle.getPublishTime());
        String nowTime = DateUtils.dateToString(new Date());
        if (publishTime.equals(nowTime)) {
            //说明这是今天的文章
            score *= 3;
        }
        //5.更新对应频道热点文章缓存
        updateArticleCache(apArticle, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getId());
        //6.更新所有热点文章缓存
        updateArticleCache(apArticle, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);


    }

    /**
     * 更新redis缓存
     * @param apArticle
     * @param score
     * @param s
     */
    private void updateArticleCache(ApArticle apArticle, Integer score, String s) {
        String hotVo = redisTemplate.opsForValue().get(s);
        boolean flag = false;
        //获取redis中的缓存数据
        if (StringUtils.isNotBlank(hotVo)){
            //转换为对应的list集合
            List<HotArticleVo> hotArticleVos = JSON.parseArray(hotVo, HotArticleVo.class);
            //遍历集合
            for (HotArticleVo hotArticleVo : hotArticleVos) {//如果id相同,就设置分数
                if (hotArticleVo.getId().equals(apArticle.getId())) {
                    hotArticleVo.setScore(score);
                    flag = true;
                }
            }
            //2 缓存中没有当前文章
            if (!flag){
                HotArticleVo hotArticleVo = new HotArticleVo();
                BeanUtils.copyProperties(apArticle,hotArticleVo);
                hotArticleVo.setScore(score);
                hotArticleVos.add(hotArticleVo);
            }
            //3.将热点文章按照分数排序,取前30条存入redis缓存中
            List<HotArticleVo> collect = hotArticleVos.stream()
                    .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                    .limit(30).collect(Collectors.toList());
            String hotString = JSON.toJSONString(collect);
            redisTemplate.opsForValue().set(s,hotString);
        }


    }

    /**
     * 按照行为进行分组
     *
     * @param updateArticleMessList
     * @return
     */
    private List<ArticleVisitStreamMess> getArticleVisitStreamMesses(List<UpdateArticleMess> updateArticleMessList) {
        List<ArticleVisitStreamMess> waitUpdateScoreData = new ArrayList<>();
        //1.根据文章id进行分组
        Map<Long, List<UpdateArticleMess>> map = updateArticleMessList.stream().collect(Collectors.groupingBy(UpdateArticleMess::getArticleId));

        //2 计算每个分组的结果
        map.forEach((articleId, messList) -> {
            //获取阅读的集合信息
            Optional<ArticleVisitStreamMess> reduce = messList.stream()
                    .map(articleMes -> {
                        ArticleVisitStreamMess visitStreamMess = new ArticleVisitStreamMess();
                        visitStreamMess.setArticleId(articleId);
                        switch (articleMes.getType()) {
                            //设置点赞
                            case LIKES:
                                visitStreamMess.setLike(articleMes.getAdd());
                                break;
                            //设置评论
                            case COMMENT:
                                visitStreamMess.setComment(articleMes.getAdd());
                                break;
                            //设置收藏
                            case COLLECTION:
                                visitStreamMess.setCollect(articleMes.getAdd());
                                break;
                            //设置阅读
                            case VIEWS:
                                visitStreamMess.setView(articleMes.getAdd());
                                break;
                        }
                        return visitStreamMess;
                    })
                    //累加
                    .reduce(new BinaryOperator<ArticleVisitStreamMess>() {
                        @Override
                        public ArticleVisitStreamMess apply(ArticleVisitStreamMess a1, ArticleVisitStreamMess a2) {
                            a1.setLike(a1.getLike() + a2.getLike());
                            a1.setView(a1.getView() + a2.getView());
                            a1.setCollect(a1.getCollect() + a2.getCollect());
                            a1.setComment(a1.getComment() + a2.getCollect());
                            return a1;
                        }
                    });
            if (reduce.isPresent()) {
                ArticleVisitStreamMess articleVisitStreamMess = reduce.get();
                log.info("热点文章 聚合计算结果  ===>{}", articleVisitStreamMess);
                waitUpdateScoreData.add(articleVisitStreamMess);
            }
        });
        return waitUpdateScoreData;
    }

    /**
     * 1. 查询redis行为列表中的行为数据
     *
     * @return
     */
    private List<UpdateArticleMess> getUpdateArticleMesses() {
        //1.获取redis中缓存的数据
        Long size = redisTemplate.opsForList().size(HotArticleConstants.HOT_ARTICLE_SCORE_BEHAVIOR_LIST);
        //2.判断
        if (size == null) {
            log.info("当前缓存中没有数据");
            CustException.cust(AppHttpCodeEnum.DATA_NOT_EXIST, "当前缓存中没有数据");
        }
        // 采用管道命令， 让多个命令保证原子性
        List list = redisTemplate.executePipelined(new RedisCallback<List<UpdateArticleMess>>() {
            @Override
            public List<UpdateArticleMess> doInRedis(RedisConnection redisConnection) throws DataAccessException {
                //开启通道命令
                redisConnection.openPipeline();
                //获取0 到 size-1 的所有集合数据 同时就是被消费掉了,数据就消失了
                redisConnection.lRange(HotArticleConstants.HOT_ARTICLE_SCORE_BEHAVIOR_LIST.getBytes(), 0, size - 1);
                //保留size 到 -1 的数据,那么就是说什么都不保留,消费多少,删掉多少
                redisConnection.lTrim(HotArticleConstants.HOT_ARTICLE_SCORE_BEHAVIOR_LIST.getBytes(), size, -1);
                return null;
            }
        }, RedisSerializer.string());

        if (list.size() > 0) {
            List<String> listDate = (List<String>) list.get(0);
            //将集合中的json字符串转换为对应的对象结合
            List<UpdateArticleMess> collect = listDate.stream().map(str -> JSON.parseObject(str, UpdateArticleMess.class)).collect(Collectors.toList());
            return collect;
        }
        return null;
    }
}
